package com.fho4565.commands;

import com.fho4565.main.Utils;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.NbtPathArgument;
import net.minecraft.commands.arguments.ObjectiveArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraft.commands.arguments.ScoreHolderArgument;
import net.minecraft.nbt.DoubleTag;

import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import java.util.function.Function;
import java.util.function.IntFunction;


public class Mathematics {
    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeIntDouble(String name, IntFunction<Double> function) {
        return Commands.literal(name)
                .then(Commands.literal("score")
                        .then(Commands.argument("baseHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("baseScore", ObjectiveArgument.objective()).executes(context -> {
                                            double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTarget", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Int", IntegerArgumentType.integer()).executes(context -> {
                                    double result = function.apply(IntegerArgumentType.getInteger(context, "Int"));
                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                    return (int) result;
                                })
                                .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                        .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(IntegerArgumentType.getInteger(context, "Int"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                            return (int) result;
                                        })))));
    }

    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeIntInt(String name, IntFunction<Integer> function) {
        return Commands.literal(name)
                .then(Commands.literal("score")
                        .then(Commands.argument("baseHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("baseScore", ObjectiveArgument.objective()).executes(context -> {
                                            int result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    int result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return result;
                                                }))))))
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTarget", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePath", NbtPathArgument.nbtPath()).executes(context -> {
                                            int result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    int result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    return result;
                                                }))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Int", IntegerArgumentType.integer()).executes(context -> {
                                    int result = function.apply(IntegerArgumentType.getInteger(context, "Int"));
                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                    return result;
                                })
                                .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                        .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                            int result = function.apply(IntegerArgumentType.getInteger(context, "Int"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                            return result;
                                        })))));
    }
    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeDoubleInt(String name, DoubleFunction<Integer> function) {
        return Commands.literal(name)
                .then(Commands.literal("score")
                        .then(Commands.argument("baseHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("baseScore", ObjectiveArgument.objective()).executes(context -> {
                                            double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTarget", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Double", DoubleArgumentType.doubleArg()).executes(context -> {
                                    double result = function.apply(DoubleArgumentType.getDouble(context, "Double"));
                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                    return (int) result;
                                })
                                .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                        .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(DoubleArgumentType.getDouble(context, "Double"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                            return (int) result;
                                        })))));
    }
    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeDoubleDouble(String name, DoubleFunction<Double> function) {
        return Commands.literal(name)
                .then(Commands.literal("score")
                        .then(Commands.argument("baseHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("baseScore", ObjectiveArgument.objective()).executes(context -> {
                                            double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore")));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTarget", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(Integer.parseInt(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Double", DoubleArgumentType.doubleArg()).executes(context -> {
                                    double result = function.apply(DoubleArgumentType.getDouble(context, "Double"));
                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                    return (int) result;
                                })
                                .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                        .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                            double result = function.apply(DoubleArgumentType.getDouble(context, "Double"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                            return (int) result;
                                        })))));
    }
    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeFloatInt(String name, Function<Float,Integer> function) {
        return Commands.literal(name)
                .then(Commands.literal("score")
                        .then(Commands.argument("baseHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("baseScore", ObjectiveArgument.objective()).executes(context -> {
                                            int result = Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    int result = Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "baseHolder"), ObjectiveArgument.getObjective(context, "baseScore"));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return result;
                                                }))))))
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTarget", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePath", NbtPathArgument.nbtPath()).executes(context -> {
                                            float result = function.apply(Float.parseFloat(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    float result = function.apply(Float.parseFloat(Utils.getData(context, "sourceTarget", "sourcePath").getAsString()));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Float", FloatArgumentType.floatArg()).executes(context -> {
                                    float result = function.apply(FloatArgumentType.getFloat(context, "Float"));
                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                    return (int) result;
                                })
                                .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                        .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                            float result = function.apply(FloatArgumentType.getFloat(context, "Float"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                            return (int) result;
                                        })))));
    }

    private static LiteralArgumentBuilder<CommandSourceStack> mathNodeDDDouble(String name, BiFunction<Double, Double, Double> function) {
        return Commands.literal(name)
                .then(Commands.literal("storage")
                        .then(Commands.argument("sourceTargetA", ResourceLocationArgument.id())
                                .then(Commands.argument("sourcePathA", NbtPathArgument.nbtPath())
                                        .then(Commands.argument("sourceTargetB", ResourceLocationArgument.id())
                                                .then(Commands.argument("sourcePathB", NbtPathArgument.nbtPath()).executes(context -> {
                                                            double result = function.apply(Double.parseDouble(Utils.getData(context, "sourceTargetA", "sourcePathA").getAsString()), Double.parseDouble(Utils.getData(context, "sourceTargetB", "sourcePathB").getAsString()));
                                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                            return (int) result;
                                                        })
                                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                                    double result = function.apply(Double.parseDouble(Utils.getData(context, "sourceTargetA", "sourcePathA").getAsString()),
                                                                            Double.parseDouble(Utils.getData(context, "sourceTargetB", "sourcePathB").getAsString()));
                                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                                    return (int) result;
                                                                }))))))))
                .then(Commands.literal("num")
                        .then(Commands.argument("Double1", DoubleArgumentType.doubleArg())
                                .then(Commands.argument("Double2", DoubleArgumentType.doubleArg()).executes(context -> {
                                            double result = function.apply(DoubleArgumentType.getDouble(context, "Double1"), DoubleArgumentType.getDouble(context, "Double2"));
                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                            return (int) result;
                                        })
                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                    double result = function.apply(DoubleArgumentType.getDouble(context, "Double1"), DoubleArgumentType.getDouble(context, "Double2"));
                                                    Utils.setData(context, "targetTarget", "targetPath", DoubleTag.valueOf(result));
                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                    return (int) result;
                                                }))))))
                .then(Commands.literal("score")
                        .then(Commands.argument("aHolder", ScoreHolderArgument.scoreHolder())
                                .then(Commands.argument("a", ObjectiveArgument.objective())
                                        .then(Commands.argument("bHolder", ScoreHolderArgument.scoreHolder())
                                                .then(Commands.argument("b", ObjectiveArgument.objective()).executes(context -> {
                                                            double result = function.apply((double) Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "aHolder"), ObjectiveArgument.getObjective(context, "a")),
                                                                    (double) Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "bHolder"), ObjectiveArgument.getObjective(context, "b")));
                                                            Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                            return (int) result;
                                                        })
                                                        .then(Commands.argument("targetTarget", ResourceLocationArgument.id())
                                                                .then(Commands.argument("targetPath", NbtPathArgument.nbtPath()).executes(context -> {
                                                                    double result = function.apply((double) Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "aHolder"), ObjectiveArgument.getObjective(context, "a")),
                                                                            (double) Utils.getScore(context.getSource().getServer(), ScoreHolderArgument.getName(context, "bHolder"), ObjectiveArgument.getObjective(context, "b")));
                                                                    Utils.sendTCdFeedback(context, "mcd.com.fho4565.command.math.success", String.valueOf(result));
                                                                    return (int) result;
                                                                }))))))));
    }

    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(
                Commands.literal("math").requires(s -> s.hasPermission(2))
                        .then(mathNodeIntDouble("log", Math::log10))
                        .then(mathNodeIntDouble("loge", Math::log))
                        .then(mathNodeIntDouble("tanh", Math::tanh))
                        .then(mathNodeIntDouble("cosh", Math::cosh))
                        .then(mathNodeIntDouble("sinh", Math::sinh))
                        .then(mathNodeIntDouble("asin", Math::asin))
                        .then(mathNodeIntDouble("acos", Math::acos))
                        .then(mathNodeIntDouble("atan", Math::atan))
                        .then(mathNodeIntDouble("tan", Math::tan))
                        .then(mathNodeIntDouble("cos", Math::cos))
                        .then(mathNodeIntDouble("sin", Math::sin))
                        .then(mathNodeDDDouble("max", Math::max))
                        .then(mathNodeDDDouble("min", Math::min))
                        .then(mathNodeIntInt("abs", Math::abs))
                        .then(mathNodeDDDouble("pow", Math::pow))
                        .then(mathNodeIntDouble("sqrt", Math::sqrt))
                        .then(mathNodeFloatInt("round", Math::round))
                        .then(mathNodeDoubleDouble("toRadians",Math::toRadians))
                        .then(mathNodeDoubleDouble("toDegrees",Math::toDegrees))
                        .then(mathNodeDoubleDouble("floor", Math::floor))
                        .then(mathNodeDoubleDouble("ceil", Math::ceil))
                        .then(mathNodeDoubleDouble("exp", Math::exp))
                        .then(mathNodeDoubleDouble("signum", Math::signum))
                        .then(mathNodeDoubleDouble("ulp", Math::ulp))
                        .then(mathNodeDoubleDouble("nextUp", Math::nextUp))
                        .then(mathNodeDoubleDouble("nextDown", Math::nextDown))
        );
    }
}